This document contains information about ODF's linking support.
Table of Contents
-------------------------
• Introduction
• Linking Update
• Part Object Changes
• Content Object Changes
• Command Object Changes
• Selection Object Changes
• Proxy Changes
• Linking Object Changes
• Fixed Linking Problems
• Framework Problems
• Sample Code Problems
• Known Linking Problems
• Framework Problems
• Sample Code Problems
Introduction
The current release represents a major, though relatively short term, effort to identify and correct problems in ODF's linking support. Many bugs, both serious and small, have been addressed.
The existing architecture is incomplete, in that it does not provide a full implementation for linking as described in the OpenDoc human interface guidelines. However, future versions of ODF will address this problem. In the meantime, the current linking code provides a solid foundation for linking. In particular, the linking related responsibilities of non-linking parts are now handled much more effectively by the framework.
This document explains the update to ODF's linking code and explains what you need to do in your part to support linking.
Linking Update
The following sections discuss the changes to ODF code and the implications these changes will have on your parts.
Part Object Changes
The FW_CPart::CloneInto method now passes a valid instance of an FW_CCloneInfo structure when it calls the Externalize method of your part's content object. In previous versions of the framework this structure was provided only by the FW_CEmbeddingPart::CloneInto method. However, parts that support linking also need this structure. As a result, the implementation of the FW_CEmbeddingPart::CloneInto method has been commented out and will be removed in the next release.
Content Object Changes
In the content object's NewPromise method, a new parameter has been added that takes an ODStorageUnit. This parameter is used only during cloning operations to test whether the storage unit contains a property of type kODPropContentFrame. If this property does not exist in the storage unit, you must return NULL from NewPromise.
Similarly, if the NewPromise method of your part's main content object is called with the storage kind of FW_kLinkStorage, you should return NULL. This is a temporary workaround for a problem involving the cloning of linked content and will hopefully be fixed in a future release.
Command Object Changes
• When a command is executed, undone, or redone, ODF calls the PropagateChanges method of the command object. This method in turn calls the part's Changed method and calls the ContentUpdated method of the affected frames. If your command objects call either of these two methods, you should remove the calls to prevent multiple updates.
• If your part defines any command objects that descend directly from FW_CCommand, you must call the IsOKtoEdit method prior to modifying your part's content. (This code is already implemented in framework command classes that implement a DoIt method such as FW_CClipboardCommand.) If this method returns false, you must stop processing of the command by calling the SetCanUndo and SetCausesChange methods (passing false to both). You should do this from the DoIt method of your command object as in the following example:
if (!this->IsOKtoEdit(ev))
{
SetCanUndo(ev, false);
SetCausesChange(ev, false);
return;
}
• If your part supports linking, you should override the IsOKtoEdit method in any command objects where it is important to take into account the effect of the command on the selected link-destination content. The overriding method should call the inherited IsOKtoEdit method first. If that method returns true, call the CanEditSelection method of the frame's selection object and return that result. The following example is taken from ODF Draw:
result = fDrawSelection->CanEditSelection(ev, GetFrame(ev));
return result;
}
Selection Object Changes
• All parts (regardless of whether they support linking) should override FW_CSelection::CanPasteLink. The current implementation of the FW_MPasteAsHandler calls this method to determine the correct setting for whether content should be merged or embedded. This setting is determined by checking for the presence or absence of the kODPropContentFrame property in the provided storage unit. If this property exists, container parts can allow the content to be embedded (kODPasteAsEmbed). If this property does not exist, you must try to merge the data with your part's content (kODPasteAsMerge).
Proxy Changes
• Proxy frames now have a new method (ChangeLinkStatus) that updates the link status of your part's embedded frames. Unlike previous versions of ODF, this method does not suppress changing the link status from kODInLinkDestination to kODInLinkSource. (This situation probably arose when a newly-created link source tried to change the status of a link destination contained within it. However, this behavior also prevented the updating of the status of a link destination whose link had been broken by the user.)
If your part changes the status of one of its links, it is also your part's responsibility to change the status of any embedded frames by calling the ChangeLinkStatus method of your proxy object. This method in turn calls the ChangeLinkStatus method of each associated proxy frame. You should change the link status of any links only if it is appropriate for your part's content.
Linking Object Changes
• You must override the LinkEstablished method of your link destination objects to update the link status of any newly-acquired embedded frames. This method is now called every time the contents of a link destination are updated (see Fixed Linking Problems for an explanation). You must use this method to establish the relationship between the newly-internalized content and your link destination object. You must also set the link status of any embedded frames to kODInLinkDestination. If your LinkUpdated method had been updating the link status of any newly-acquired embedded frames, you should move that code into your LinkEstablished method.
• Parts that embed and link should not implement the NewPromise method in their link source content class due to a recently discovered problem. The problem occurs when content is dragged from the finder and dropped into a container that is embedded in a link source object that supports promises. An alternative to not implementing the NewPromise method is to simply return NULL from NewPromise when the storage kind is FW_kLinkStorage.
• With regards to breaking and restoring links, parts now need either to preserve the content of a broken link or to force an update when restoring a link whose creation was undone.
Fixed Linking Problems
The following sections describe the linking problems that have been fixed for this release.
Framework Changes
• FW_CPart::CloneInto now determines the storage kind that is being used for a cloning operation. The old version would simply use the type FW_kPartStorage every time. As a result of this fix, the part's dirty flag is no longer cleared as the result of a cloning operation that was triggered by drag and drop or the clipboard. This fix also makes it possible to use promises when your part's content is being cloned. (Another bug prevents the use of promises when writing out content to a link storage unit during a cloning operation. See the Known Problems section for more information.)
• FW_CCommand::IsOKtoEdit, FW_CSelection::CanEditSelection, and various supporting methods have been corrected and refactored. This fixes problems that would result in displaying two dialogs (one after another) when attempting to edit the content of a link destination. This also fixes a reverse-logic problem that prevented a command from changing the part when the user broke the link and allowed the command to change the part when the link remained intact.
• The method FW_CProxyFrame::ChangeLinkStatus replaces direct calls to ODFrame::ChangeLinkStatus. This method updates the frame's link status only once the ODFrame object is in memory and has had its containing frame set. This method is also called when a detached frame is reattached. This method ensures that frames have their linkstatus set without defeating lazy internalization. This method also fixes crashes that occurred when the ODFrame::ChangeLinkStatus method was called on a frame whose containing frame was not yet set.
• ODF now calls the FW_CLinkDestination::LinkEstablished method is called by the FW_CLinkDestination::LinkUpdated method each time the contents of the link are updated. This method used to be called only when the link was first established. However, the LinkEstablished method offers the only opportunity for newly-embedded frames (acquired during the update) to have their link status set to kODInLinkDestination.
• FW_CLinkDestination::ShowLinkInfo now calls the Unregister method when the user changes the link update setting from automatic to manual. In previous versions, this method was not called and was causing broken links to continue to receive updates. This fix also prevents the same part from being registered twice with the same ODLink, and therefore causing redundant updates.
• FW_CLinkDestination::Register now saves a reference to the part regardless of whether or not it registers that part. In accordance, the Unregister method does not clear this reference. Without this information, parts would crash when the user changed the link update setting from manual to automatic. Changing the link update setting to manual used to clear the part reference. When this setting was changed back to manual, the link would call Register and pass a null value for the part, thus causing the crash.
Also, the Register method no longer increments the reference count of the underlying ODPart object when it stores a reference to the part. This was overkill.
• FW_CLinkSource::ShowLinkInfo now uses the update ID in its fUpdateID field rather than calling the ODLinkSource::GetUpdateID method. ShowLinkInfo uses this update ID when it calls the ODLinkSource::ShowLinkSourceInfo method. The old behavior prevented the enabling of the Update Now button in the dialog. The new behavior fixes that problem.
• FW_CLinkSource::ShowLinkInfo now generates a new update ID when the user clicks the Update Now button for a manual link. It then proceeds to pass this update ID to the ContentUpdated method. This fix prevents the display of the Link Cycle dialog box by OpenDoc when manually updating a link that is part of a cycle or false cycle.
Similarly, when the user changes a manual link to On Save, a new ID is generated. This action is semantically equivalent to choosing Update Now and then changing the link update setting to automatic. The previous behavior called ODLinkSource::GetUpdateID, which caused the Link Cycle dialog box to be displayed.
• FW_CLinkDestination::Register now requests automatic updates for the link if it is either automatic or if the link is not yet established. If the link should be updated manually, ODF unregisters the link after it has received its first update. This new behavior always allows a cross-document link to obtain its first update.
• FW_CLinkSource::ContentUpdated now uses the link's existing update ID when the updateID parameter is equal to kODUnknownUpdate and the forceUpdate parameter is true. This situation only arises when the FW_CLinkManager::CreateLink method is called for a link source that has already been established. The ContentUpdated method clears and rewrites the linked data to the storage unit. Using the existing ID prevents the generation of unnecessary update events to existing destinations if the content has not changed since the last update.
• FW_CODPart::LinkUpdated calls FW_CPart::Changed after updating links. This fix makes sure that the part is marked as dirty so that changes to the link destinations will be saved.
• Removed the FW_CLinkSource::ResolveAllPromises method and modified the FW_CDataInterchange and FW_CLinkSource classes to correctly supported promised content with links. Linkpromises are more like drag and drop promises than clipboard promises in that whatever values are required are pretty much resolved synchronously. It is never necessary to force promise resolution as it is with the clipboard. The ResolveAllPromises method had the unfortunate side-effect of causing the ODLinkSource object to immediately fulfill all promises rather than just those in use. This behavior, of course, defeated the purpose of using promises with links.
• The FW_CLinkManager methods AddToDestLinkList, RemoveFromDestLinkList, AddToSourceLinkList, and RemoveFromSourceLInkList are now safe to be called redundantly.
• The FW_CLinkManager::EditInLinkAlert method will not display a background alert box if the user attempts to drop content into a container part that is in a link destination in a background document.
• FW_CDragCommand::BeginDrag no longer suppresses the creation of a link spec if the command key is not depressed when the drag is initiated. OpenDoc determines the drop attribute that triggers the paste-as dialog by the presence of the command key when the drop is made.
• ODF no longer breaks the destination link when the action of creating the link is undone. Similarly, ODf no longer restores the destination link when the action of creating the link is redone. Breaking and restoring the link caused the unnecessary resetting of the link status of any embedded frames. The current implementation simply removes the link from the part's internal data structures and unregisters/registers it.
• ODF no longer propagates changes to the containing frame's ContentUpdated method when links are established, broken, and reestablished. Contained links are never reflected in updates to the containing link source and its destinations. Therefore, only the Changed method of this part is called so that the changes can be saved with the document.
• The FW_CLinkDestination::RestoreLink method no longer forces a restored link to be updated if the linked content has not changed. The part should not have disposed of the linked content when the link was broken. Therefore, updating the link is unnecessary.
• The FW_CLinkManager::DoUpdateLinks method now passes false to the forceUpdate parameter of FW_CLinkSource::ContentUpdated. In previous versions, changes to an embedded part that was in a manually updated link would still cause the link to update automatically.
• Linking-related command objects are now posted in the proper order. Because linking involves multiple command objects, it is important that these objects get posted to the part's action history in the correct order. Previously, situations could arise where the end action was posted too soon, separating other command objects from the action, or never posted at all, causing all subsequent user actions to accumulate as a single undo item.
Sample Code Changes
• ODFDraw and ODFTable now respond correctly when an embedded frame in a link source is updated. An incorrect test for the validity of the ODFrame being updated was suppressing the updating of link sources containing that frame.
• In general, all the sample programs take appropriate actions as a result of changes attempted or completed when embedded in a link source or link destination. Updating containing link sources is handled automatically by FW_CCommand::PropagateChanges.
Protecting content from changes when embedded in a link destination is handled by calling IsOKtoEdit. For the most part, only parts that support linking need to override these methods.
• When necessary, ODFDraw commands override the PropagateChanges method to call CDrawSelection::SelectionChanged so that link sources can be updated. However, the use of this method has been removed from a number of cases that cannot actually affect the link-source content.
The method is used only when an existing content object is modified. Actions that add new content can't affect existing link sources in ODFDraw. Actions that remove existing content can't rely on this method because once the content is removed from the selection object, it no longer has any record of what links require updating. Instead, SaveUndoState obtains a list of affected link sources from the selection using the new GetSelectedLinkSources method, and this is used for updating in PropagateChanges when it is called by FW_CCommand.
Because it is required that both local links, and containing frames be updated with the same ODUpdateID value, overrides of PropagateChanges that call SelectionChanged generate a new unique ID and pass this to both SelectionChanged and the inherited method.
• ODFDraw: CDrawPartContent overrides NewPromise to promise the part's content when appropriate to do so. The current implementation avoids promising to FW_kLinkStorage due to a newly discovered OpenDoc bug.
• ODFDraw: CDrawLinkSourceContent now overrides NewPromise to promise intrinsic link content. The class has been made a subclass of CDrawPromiseContent, which allows it to promise itself instead of copying itself and passing the copy to the CDrawPromise constructor. ~CDrawPromise respects this by not deleting the promised content when fStorageKind == FW_kLinkStorage.
• ODFDraw: CBaseShape::RestoreShape avoids adding 'subscribed' shapes to the part's content, unless the shape is a 'group shape'. Doing so was leading to a crash after a link updated, leaving stale pointers to its deleted content in the part.
• ODFDraw: CDrawLinkManager::SelectSubscribedShapes has been deleted. The implementation had the effect of adding the individual shapes involved in a link destination to the selection object. Closing the selection after updating a link that was created using a drag or drop command would cause a crash. The CDrawDropCommand::DoPasteAs method now uses the link's SelectShapes method, which handles the situation correctly.
• ODFDraw and ODFTable. The Paste As command is now enabled whenever Paste is enabled. It used to be enabled only when there was a link spec on the clipboard, but the command has other purposes than just creating links.
• ODFDraw: CDrawSelection::CanPasteLink now checks for kODPropContentFrame to determine the default setting when content could be merged.
• All linking or container samples override CanPasteLink if they did not do so previously.
• ODFDraw: CDrawPublishLink::ExternalizeLinkContent no longer explicitly removes kODPropContentFrame and kODPropCloneKindUsed. This was a work around for a long ago fixed shortcoming in ODLinkSource::Clear, which is called by FW_CLinkSource::ContentUpdated.
• ODFDraw and ODFTable: Changes in the drop and clipboard commands, and in CDrawSubscribeLink, now preserve content acquired after a link was created. They also restore this content if the link creation is 'undone' and 'redone' instead of requiring the link to update.
Known Linking Problems
The following sections list the known problems with linking in both the ODF framework code and the ODF sample code.
Framework Problems
• If a cloning operation is taking place, your part's main content object must not create a promise object if the storageKind is FW_kLinkStorage. OpenDoc is currently unable to resolve promises made to a link during a drop. When the promise is made during a cloning operation, the resultant failure inevitably leads to a crash.
• The current linking design does not support transferring links from a subset of the part's content via the clipboard and drag and drop. This behavior will be added to a future release of ODF.
• FW_CDragCommand::BeginDrag does not take into account the possibility that a move may be prohibited, such as when the dragged content is contained in a link destination. This method should call the IsOKtoEdit method if the drag results in a drag-move. If IsOKtoEdit returns false, BeginDrag should call ODUndo::AbortCurrentTransaction to return both the drag source and drop target to their previous states.
• The FW_CLinkDestination::LinkUpdated method currently calls the DoPropagateChanges method, which in turn calls the FW_Presentation::ContentUpdated method. If multiple link destinations share the same ODLink object, DoPropagateChanges should not be called until all of the link destinations have been updated. Theoretically, the problem is that they may have different presentations (frames).
• FW_CODPart and FW_CLinkDestination::LinkUpdated don't handle an initial update failure appropriately. This error occurs when the ODLink::GetContentStorageUnit method throws an exception with the error value kODErrCannotEstablishLink. When this exception is thrown, ODF should remove the link from the part and abort the pending undo transaction.
This error condition occurs after the creation of a cross-document link when the part fails to create a link source. Although this is a relatively unlikely occurrence, it needs to be handled. OpenDoc's embedding support provides unlimited opportunity for interacting with 'unknown quantities'.
Sample Code Problems
• ODFDraw never enables the Link Info menu item.
• ODFDraw and ODFTable do not transfer links via the clipboard or drag and drop.
• ODFDraw does not handle resolution of clipboard promises correctly when a promised shape is removed from the part's content (this is not linking specific).
• ODFDraw does not allow the creation of multiple link destinations from the same link source by consecutive Paste-As from the same clipboard.
• ODFDraw does not allow the creation of multiple link destinations from the same source. These links would be created by performing a Copy and Paste As operation or by performing a drag-move operation of an existing link source multiple times.
• ODFDraw does not allow the user to select and copy portions of a link destination.
• ODFDraw and ODFTable do not display link borders.
• ODFDraw does not allow overlapping link sources. ODFDraw also does not allow a link source to contain a link destination.